// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

/// And end-to-end test that generates code and checks that the output matches
/// the code in `static_test.dart`. Techincally we could run the result in an
/// isolate, but instead we decided to split that up in two tests. This test
/// ensures that we generate the code as it was written in static_test, and
/// separately static_test ensures that the smoke.static library behaves as
/// expected.
library smoke.test.codegen.end_to_end_test;

import 'dart:io';

import 'package:analyzer/src/generated/element.dart';
import 'package:smoke/codegen/generator.dart';
import 'package:smoke/codegen/recorder.dart';
import 'package:unittest/unittest.dart';
import 'package:path/path.dart' as path;

import 'testing_resolver_utils.dart' show initAnalyzer;

void main(List<String> args) {
  final updateStaticTest = args.length > 0 && args[0] == '--update_static_test';

  test('static_test is up to date', () {
    var scriptPath = path.fromUri(Platform.script);
    var testDir = path.dirname(path.dirname(scriptPath));
    var commonPath = path.join(testDir, 'common.dart');
    var testCode = new File('$commonPath').readAsStringSync();
    var lib = initAnalyzer({'common.dart': testCode}).libraryFor('common.dart');
    var generator = new SmokeCodeGenerator();
    var recorder = new Recorder(generator, _resolveImportUrl);

    lookupMember(String className, String memberName, bool recursive) {
      recorder.lookupMember(lib.getType(className), memberName,
          recursive: recursive, includeAccessors: false);
    }

    runQuery(String className, QueryOptions options) {
      recorder.runQuery(lib.getType(className), options,
          includeAccessors: false);
    }

    // Record all getters and setters we use in the tests.
    [
      'i',
      'j',
      'j2',
      'inc0',
      'inc1',
      'inc2',
      'toString'
    ].forEach(generator.addGetter);
    ['i', 'j2'].forEach(generator.addSetter);

    // Record static methods used in the tests
    recorder.addStaticMethod(lib.getType('A'), 'staticInc');

    // Record symbol convertions.
    generator.addSymbol('i');

    /// Record all parent-class relations that we explicitly request.
    [
      'AnnotB',
      'A',
      'B',
      'D',
      'H'
    ].forEach((className) => recorder.lookupParent(lib.getType(className)));

    // Record members for which we implicitly request their declaration in
    // has-getter and has-setter tests.
    lookupMember('A', 'i', true);
    lookupMember('A', 'j2', true);
    lookupMember('A', 'inc2', true);
    lookupMember('B', 'a', true);
    lookupMember('B', 'f', true);
    lookupMember('D', 'i', true);
    lookupMember('E', 'y', true);

    // Record also lookups for non-exisiting members.
    lookupMember('B', 'i', true);
    lookupMember('E', 'x', true);
    lookupMember('E', 'z', true);

    // Record members for which we explicitly request their declaration.
    lookupMember('B', 'a', false);
    lookupMember('B', 'w', false);
    lookupMember('A', 'inc1', false);
    lookupMember('F', 'staticMethod', false);
    lookupMember('G', 'b', false);
    lookupMember('G', 'd', false);

    // Lookups from no-such-method test.
    lookupMember('A', 'noSuchMethod', true);
    lookupMember('E', 'noSuchMethod', true);
    lookupMember('E2', 'noSuchMethod', true);

    // Lookups from has-instance-method and has-static-method tests.
    lookupMember('A', 'inc0', true);
    lookupMember('A', 'inc3', true);
    lookupMember('C', 'inc', true);
    lookupMember('D', 'inc', true);
    lookupMember('D', 'inc0', true);
    lookupMember('F', 'staticMethod', true);
    lookupMember('F2', 'staticMethod', true);

    // Record all queries done by the test.
    runQuery('A', new QueryOptions());
    runQuery('D', new QueryOptions(includeInherited: true));

    var vars = lib.definingCompilationUnit.topLevelVariables;
    expect(vars[0].name, 'a1');
    expect(vars[1].name, 'a2');

    runQuery('H', new QueryOptions(
        includeInherited: true,
        withAnnotations: [vars[0], vars[1], lib.getType('Annot')]));

    runQuery('K', new QueryOptions(
        includeInherited: true, withAnnotations: [lib.getType('AnnotC')]));

    var code = _createEntrypoint(generator);
    var staticTestFile = new File(path.join(testDir, 'static_test.dart'));
    var existingCode = staticTestFile.readAsStringSync();
    if (!updateStaticTest) {
      expect(code, existingCode);
    } else if (code == existingCode) {
      print('static_test.dart is already up to date');
    } else {
      staticTestFile.writeAsStringSync(code);
      print('static_test.dart has been updated.');
    }
  });
}

String _createEntrypoint(SmokeCodeGenerator generator) {
  var sb = new StringBuffer()
    ..writeln('/// ---- AUTOGENERATED: DO NOT EDIT THIS FILE --------------')
    ..writeln('/// To update this test file, call:')
    ..writeln('/// > dart codegen/end_to_end_test.dart --update_static_test')
    ..writeln('/// --------------------------------------------------------')
    ..writeln('\nlibrary smoke.test.static_test;\n')
    ..writeln("import 'package:unittest/unittest.dart';");

  generator.writeImports(sb);
  sb.writeln("import 'common.dart' as common show main;\n");
  generator.writeTopLevelDeclarations(sb);
  sb.write('\nfinal configuration = ');
  generator.writeStaticConfiguration(sb, 0);

  sb
    ..writeln(';\n')
    ..writeln('main() {')
    ..writeln('  setUp(() => useGeneratedCode(configuration));')
    ..writeln('  common.main();')
    ..writeln('}');
  return sb.toString();
}

String _resolveImportUrl(LibraryElement lib) {
  if (lib.isDartCore) return 'dart:core';
  if (lib.displayName == 'smoke.test.common') return 'common.dart';
  return 'unknown.dart';
}
